package edu.pitt.dbmi.odie.gapp.gwt.client.navigator;

import java.util.ArrayList;
import java.util.Iterator;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.core.Rectangle;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.layout.VLayout;

public class ODIE_GwtDocumentViewer extends VLayout implements ClickHandler {

	/*
	 * The hierarchy of containers is as follows
	 * 
	 * self (Canvas) ScrollPanelCanvasWrapper ScrollPanel FlowPanel (many) HTML
	 */

	protected static final int STATE_PLAIN_TEXT = 0;
	protected static final int STATE_ANNOTATION_TEXT = 1;

	protected ODIE_NavigatorPanel navigatorPanel = null;

	protected int currentLine = -1;

	protected ArrayList<HTML> htmlFragments = new ArrayList<HTML>();

	protected ODIE_NavigatableAnnotation currentAnnotation = null;
	protected int currentAnnotationIndex = -1;

	protected ScrollPanel documentPanel = null;
	protected FlowPanel metaDataPanel = null;

	private boolean isGeneratingTestAnnotations = false;

	public ODIE_GwtDocumentViewer() {
		setOverflow(Overflow.HIDDEN);
	}

	private Canvas cachedDocumentCanvasWrapper = null;

	private ScrollPanel cachedHtmlScrollPanel = null;

	private Panel cachedHtmlFlowPanel = null;
	
	private ArrayList<ODIE_NavigatableAnnotation> annotations ;

	public void resetCachedDocumentCanvasWrapper(
			Canvas newCachedDocumentCanvasWrapper) {
		if (newCachedDocumentCanvasWrapper != null) {
			if (this.cachedDocumentCanvasWrapper != null) {
				removeMember(this.cachedDocumentCanvasWrapper);
			}
			this.cachedDocumentCanvasWrapper = newCachedDocumentCanvasWrapper;
			this.cachedDocumentCanvasWrapper.setOverflow(Overflow.AUTO);
			addMember(this.cachedDocumentCanvasWrapper);
			redraw();
		}
	}

	public void annotateAndConvertDocument(ODIE_NavigatorPanel navigatorPanel,
			String currentDocumentContent,
			ArrayList<ODIE_NavigatableAnnotation> annotations) {
		this.annotations = annotations ;
		this.navigatorPanel = navigatorPanel;
		FlowPanel newHtmlFlowPanel = new FlowPanel();
		newHtmlFlowPanel.setWidth("100%");
		this.currentLine = 0;
		int readingState = STATE_PLAIN_TEXT;
		this.currentAnnotation = null;
		Iterator<ODIE_NavigatableAnnotation> annotationsIterator = null;
		if (annotations != null && annotations.size() > 0) {
			annotationsIterator = annotations.iterator();
			currentAnnotation = (ODIE_NavigatableAnnotation) annotationsIterator
					.next();
		}
		// Log.debug(navigatableDoc.getText());
		char[] documentChars = currentDocumentContent.toCharArray();
		int currentCharacterIndex = 0;
		ArrayList<Character> htmlBuffer = new ArrayList<Character>();
		this.htmlFragments.clear();
		while (currentCharacterIndex < documentChars.length) {
			char currentCharacter = documentChars[currentCharacterIndex];
			if (readingState == STATE_PLAIN_TEXT) {
				if (currentAnnotation != null
						&& currentCharacterIndex == currentAnnotation
								.getStartOffset()) {
					newHtmlFlowPanel.add(flushBufferToHTML(htmlBuffer,
							"odie-DocumentText"));
					currentAnnotation.setLineOffset(currentLine);
					String idPart = "id=Annotation-"
							+ currentAnnotation.getAnnotationId();
					// + currentAnnotation.getStartOffset() + "-"
					// + currentAnnotation.getEndOffset() ;
					String linkHtmlString = "<a ";
					linkHtmlString += idPart;
					linkHtmlString += " text-decoration='none' ";
					String annotationBackgroundColor = (currentAnnotation
							.getColor() == null) ? "#CA1F7B"
							: currentAnnotation.getColor();
					// linkHtmlString += " background-color='" +
					// annotationBackgroundColor + "' " ;
					linkHtmlString += " background="
							+ annotationBackgroundColor + " ";
					linkHtmlString += " >";
					// Log.debug("Annotation link ==> " +
					// linkHtmlString) ;
					appendHtml(htmlBuffer, linkHtmlString);
					readingState = STATE_ANNOTATION_TEXT;
				}
			}
			if (readingState == STATE_ANNOTATION_TEXT) {
				if (this.currentAnnotation != null
						&& currentCharacterIndex == this.currentAnnotation
								.getEndOffset() + 1) {
					appendHtml(htmlBuffer, "</a>");
					readingState = STATE_PLAIN_TEXT;
					HTML annotationHyperLink = flushBufferToHTML(htmlBuffer,
							"odie-Annotation");
					annotationHyperLink.addClickHandler(this);
					this.htmlFragments.add(annotationHyperLink);
					newHtmlFlowPanel.add(annotationHyperLink);
					if (annotationsIterator.hasNext()) {
						currentAnnotation = (ODIE_NavigatableAnnotation) annotationsIterator
								.next();
					}
				}
			}
			if (currentCharacter == '\n') {
				this.currentLine++;
				appendHtml(htmlBuffer, "<br>");
			} else if (currentCharacter == ' ') {
				appendHtml(htmlBuffer, "&nbsp;");
			} else {
				htmlBuffer.add(new Character(currentCharacter));
			}
			currentCharacterIndex++;
		}
		Log.debug(flushBufferToString(htmlBuffer));
		newHtmlFlowPanel
				.add(flushBufferToHTML(htmlBuffer, "odie-DocumentText"));
		// Log.debug(DOM.getInnerHTML(documentHtmlPanel.getElement()));

		ScrollPanel newHtmlScrollPanel = new ScrollPanel();
		newHtmlScrollPanel.setWidth("100%") ;
		newHtmlScrollPanel.add(newHtmlFlowPanel);

		Canvas newDocumentCanvasWrapper = new Canvas();
		newDocumentCanvasWrapper.setWidth100();
		newDocumentCanvasWrapper.setMargin(20);
		newDocumentCanvasWrapper.addChild(newHtmlScrollPanel);

		cachedHtmlFlowPanel = newHtmlFlowPanel;
		cachedHtmlScrollPanel = newHtmlScrollPanel;

		resetCachedDocumentCanvasWrapper(newDocumentCanvasWrapper);

	}

	protected ODIE_NavigatableDocument generateTestAnnotations(
			ODIE_NavigatableDocument navigatableDoc) {
		if (isGeneratingTestAnnotations) {
			if (navigatableDoc.getAnnotations() == null) {
				navigatableDoc
						.setAnnotations(new ArrayList<ODIE_NavigatableAnnotation>());
			}
			if (navigatableDoc.getAnnotations().isEmpty()
					&& isGeneratingTestAnnotations) {
				String documentText = navigatableDoc.getText();
				int textLen = documentText.length();
				boolean annotationToggle = false;
				int annotationLength = 9;
				int idx = 0;
				for (; idx < textLen;) {
					if (annotationToggle) {
						int startPosition = idx;
						int endPosition = idx + annotationLength;
						if (endPosition < textLen) {
							ODIE_NavigatableAnnotation annotation = new ODIE_NavigatableAnnotation();
							annotation.setStartOffset(startPosition);
							annotation.setEndOffset(endPosition);
							annotation
									.setUri("http://caties.cabig.upmc.edu/ODIE/ontologies/2008/1/1/molecule.owl#Enzyme");
							navigatableDoc.getAnnotations().add(annotation);
						}
						annotationToggle = false;
					} else {
						annotationToggle = true;
					}
					idx += annotationLength;
				}
			}
		}
		return navigatableDoc;
	}

	protected HTML flushBufferToHTML(ArrayList<Character> htmlBuffer,
			String styleName) {
		String result = "";
		for (Iterator<Character> resultIterator = htmlBuffer.iterator(); resultIterator
				.hasNext();) {
			result += resultIterator.next();
		}
		htmlBuffer.clear();
		HTML resultHTML = new HTML(result);
		resultHTML.setStyleName(styleName);
		resultHTML.setStylePrimaryName(styleName);

		if (styleName.startsWith("odie-Annotation")
				&& this.currentAnnotation != null) {
			String annotationBackgroundColor = (currentAnnotation.getColor() == null) ? "#CA1F7B"
					: currentAnnotation.getColor();
			if (!annotationBackgroundColor.startsWith("#")) {
				annotationBackgroundColor = "#" + annotationBackgroundColor;
			}
			DOM.setStyleAttribute(resultHTML.getElement(), "background",
					annotationBackgroundColor);
		}
		if (styleName.toLowerCase().endsWith("selected")) {
			DOM.setStyleAttribute(resultHTML.getElement(), "border",
					"5px, solid, black");
		}
		return resultHTML;
	}

	protected String flushBufferToString(ArrayList<Character> htmlBuffer) {
		String result = "";
		String breakBuffer = "";
		for (Iterator<Character> resultIterator = htmlBuffer.iterator(); resultIterator
				.hasNext();) {
			char c = ((Character) resultIterator.next()).charValue();
			breakBuffer += c;
			result += c;
			if (breakBuffer.equalsIgnoreCase("<br>")) {
				result += "\n";
				breakBuffer = "";
			}
		}
		return result;
	}

	protected ArrayList<Character> appendHtml(ArrayList<Character> htmlBuffer,
			String html) {
		char[] htmlArray = html.toCharArray();
		for (int idx = 0; idx < htmlArray.length; idx++) {
			htmlBuffer.add(new Character(htmlArray[idx]));
		}
		return htmlBuffer;
	}

	public void onClick(ClickEvent event) {
		HTML htmlFragment = (HTML) event.getSource();
		String html = htmlFragment.getHTML();
		String annotationId = getAnnotationIdFromHTML(html);
		int htmlIdx = getHTMLIndexFromId(annotationId);
		if (htmlIdx != this.currentAnnotationIndex) {
			deSelectCurrentAnnotationIndex();
			this.currentAnnotationIndex = htmlIdx;
			this.currentAnnotation = this.annotations.get(this.currentAnnotationIndex) ;
			selectCurrentAnnotationIndex();
		}
		this.navigatorPanel.positionAnnotation(annotationId);
	}

	private int getHTMLIndexFromId(String id) {
		Iterator<HTML> iterator = this.htmlFragments.iterator();
		int idx = -1;
		while (iterator.hasNext()) {
			idx++;
			String html = iterator.next().getHTML();
			String htmlId = getAnnotationIdFromHTML(html);
			if (htmlId.equals(id)) {
				break;
			}
		}
		return idx;
	}

	public void navigateToAnnotation(String id) {
		deSelectCurrentAnnotationIndex();
		this.currentAnnotationIndex = getHTMLIndexFromId(id);
		this.currentAnnotation = this.annotations.get(this.currentAnnotationIndex) ;
		selectCurrentAnnotationIndex();
	}

	public void scrollToCurrentAnnotation(
			ODIE_NavigatableAnnotation currentAnnotation, Canvas scrollingCanvas) {
		if (currentAnnotation != null) {
			int height = scrollingCanvas.getHeight();
			int innerHeight = scrollingCanvas.getInnerHeight();
			int innerContentHeight = scrollingCanvas.getInnerContentHeight();
			int minHeight = scrollingCanvas.getMinHeight();
			int maxHeight = scrollingCanvas.getMaxHeight();
			int offsetHeight = scrollingCanvas.getOffsetHeight();
			int offsetY = scrollingCanvas.getOffsetY();
			int currentLineFromAnnotation = currentAnnotation.getLineOffset();
			int scrollBarSize = scrollingCanvas.getScrollbarSize();
			int scrollBottom = scrollingCanvas.getScrollBottom();
			int scrollHeight = scrollingCanvas.getScrollHeight();
			int scrollTop = scrollingCanvas.getScrollTop();
			Rectangle rect = scrollingCanvas.getRect();
			StringBuffer sb = displayScrollDiagnostics(rect, height,
					innerHeight, innerContentHeight, minHeight,
					maxHeight, offsetHeight, offsetY, currentLine,
					scrollBarSize, currentLineFromAnnotation, scrollTop,
					scrollBottom, scrollHeight) ;

			double currentLine = currentLineFromAnnotation;
			double position2lineRatio = 1.0d;
			double currentOffsetY = offsetY;
			if (currentOffsetY > 0.0d) {
				position2lineRatio = currentLine / currentOffsetY;
			}
			position2lineRatio = 0.048d;
			position2lineRatio = 0.075d;

			sb.append("position2lineRatio = " + position2lineRatio);
			sb.append("\n");
			
			double currentScrollStartPosition = scrollingCanvas.getScrollTop();
			double currentScrollEndPosition = scrollingCanvas.getInnerHeight()
					+ currentScrollStartPosition;
			double lineStartOffset = currentScrollStartPosition
					* position2lineRatio;
			double lineEndOffset = currentScrollEndPosition
					* position2lineRatio;
			double currentPosition = 0.0;
			if (currentLine < lineStartOffset) {
				double resetScrollStartPosition = Math.max(currentLine - 5.0d,
						0.0d)
						/ position2lineRatio;
				if (resetScrollStartPosition < 0.0) {
					resetScrollStartPosition = 0.0;
				}
				Log.debug("Must scroll to position "
						+ resetScrollStartPosition);
				scrollingCanvas.scrollTo(0, (int) resetScrollStartPosition);
			} else if (currentLine > lineEndOffset) {
				double resetScrollStartPosition = (currentLine + 5.0d)
						/ position2lineRatio;
				// Assume overflow is handled by the scroll panel
				// Log.debug("Scrolling forward to to position " +
				// resetScrollStartPosition) ;
				Log.debug("Must scroll to position "
						+ resetScrollStartPosition);
				scrollingCanvas.scrollTo(0, (int) resetScrollStartPosition);
			}
		}
	}

	private StringBuffer displayScrollDiagnostics(Rectangle rect, int height,
			int innerHeight, int innerContentHeight, int minHeight,
			int maxHeight, int offsetHeight, int offsetY, int currentLine,
			int scrollBarSize, int currentLineFromAnnotation, int scrollTop,
			int scrollBottom, int scrollHeight) {
		StringBuffer sb = new StringBuffer();
		sb.append("rect = " + rect.getTop() + ", " + rect.getHeight());
		sb.append("\n");
		sb.append("height = " + height);
		sb.append("\n");
		sb.append("innerHeight = " + innerHeight);
		sb.append("\n");
		sb.append("innerContentHeight = " + innerContentHeight);
		sb.append("\n");
		sb.append("minHeight = " + minHeight);
		sb.append("\n");
		sb.append("maxHeight = " + maxHeight);
		sb.append("\n");
		sb.append("offsetHeight = " + offsetHeight);
		sb.append("\n");
		sb.append("offsetY = " + offsetY);
		sb.append("\n");
		sb.append("currentLine = " + currentLineFromAnnotation);
		sb.append("\n");
		sb.append("scrollBarSize = " + scrollBarSize);
		sb.append("\n");
		sb.append("scrollTop = " + scrollTop);
		sb.append("\n");
		sb.append("scrollBottom = " + scrollBottom);
		sb.append("\n");
		sb.append("scrollHeight = " + scrollHeight);
		sb.append("\n");
		return sb ;
	}

	public void scrollToCurrentGwtAnnotation(
			ODIE_NavigatableAnnotation currentAnnotation) {
		if (currentAnnotation != null) {
			double currentScrollStartPosition = cachedHtmlScrollPanel
					.getScrollPosition();
			double currentScrollOffsetHeight = cachedHtmlScrollPanel
					.getOffsetHeight();
			double currentScrollEndPosition = currentScrollStartPosition
					+ currentScrollOffsetHeight;
			double currentDisplayEndPosition = cachedHtmlFlowPanel.getElement()
					.getOffsetHeight();
			double currentLine = currentAnnotation.getLineOffset();
			double totalLines = getCurrentLine();

			double position2lineRatio = 1.0d;
			if (totalLines > 0 && currentDisplayEndPosition > 0) {
				position2lineRatio = totalLines / currentDisplayEndPosition;
			}
			// Log.debug("ss, se, ds, de, al, tl, ratio = "
			// + currentScrollStartPosition + ", "
			// + currentScrollEndPosition + ", "
			// + 0.0 + ", "
			// + currentDisplayEndPosition + ", "
			// + currentLine + ", "
			// + totalLines + ", "
			// + position2lineRatio
			// ) ;
			double lineStartOffset = currentScrollStartPosition
					* position2lineRatio;
			double lineEndOffset = currentScrollEndPosition
					* position2lineRatio;
			// Log.debug("Currently displaying lines = "
			// + lineStartOffset + ", "
			// + lineEndOffset
			// ) ;
			double currentPosition = currentLine / position2lineRatio;
			if (currentLine < lineStartOffset) {
				double resetScrollStartPosition = currentPosition;
				if (resetScrollStartPosition < 0.0) {
					resetScrollStartPosition = 0.0;
				}
				// Log.debug("Backing up to position " +
				// resetScrollStartPosition) ;
				cachedHtmlScrollPanel
						.setScrollPosition((int) resetScrollStartPosition);
			} else if (currentLine > lineEndOffset) {
				double resetScrollStartPosition = currentPosition;
				// Assume overflow is handled by the scroll panel
				// Log.debug("Scrolling forward to to position " +
				// resetScrollStartPosition) ;
				cachedHtmlScrollPanel
						.setScrollPosition((int) resetScrollStartPosition);
			}
		}
	}

	private void selectCurrentAnnotationIndex() {
		styleCurrentAnnotation("odie-Annotation-Selected");
		scrollToCurrentAnnotation(this.currentAnnotation,
				this.cachedDocumentCanvasWrapper);
	}

	private void deSelectCurrentAnnotationIndex() {
		styleCurrentAnnotation("odie-Annotation");
	}

	private void styleCurrentAnnotation(String styleName) {
		if (this.htmlFragments != null && htmlFragments.size() > 0
				&& this.currentAnnotationIndex >= 0 
				&& this.currentAnnotationIndex < htmlFragments.size()) {
			HTML htmlAnnotationFragment = (HTML) this.htmlFragments
					.get(this.currentAnnotationIndex);
			htmlAnnotationFragment.setStylePrimaryName(styleName);
		}
	}

	public void getAnnotationForWidget(Widget sender) {
		deSelectCurrentAnnotationIndex();
		Iterator<HTML> iterator = this.htmlFragments.iterator();
		int idx = -1;
		while (iterator.hasNext()) {
			idx++;
			if (iterator.next() == sender) {
				this.currentAnnotationIndex = idx;
				break;
			}
		}
		selectCurrentAnnotationIndex();
	}

	public boolean isGeneratingTestAnnotations() {
		return isGeneratingTestAnnotations;
	}

	public void setGeneratingTestAnnotations(boolean isGeneratingTestAnnotations) {
		this.isGeneratingTestAnnotations = isGeneratingTestAnnotations;
	}

	public ODIE_NavigatableAnnotation getCurrentAnnotation() {
		return currentAnnotation;
	}

	public void setCurrentAnnotation(
			ODIE_NavigatableAnnotation currentAnnotation) {
		this.currentAnnotation = currentAnnotation;
	}

	public int getCurrentLine() {
		return currentLine;
	}

	public void setCurrentLine(int currentLine) {
		this.currentLine = currentLine;
	}

	private String getAnnotationIdFromHTML(String html) {
		String id = html.replaceAll("^.*id=Annotation-", "");
		id = id.substring(0, id.indexOf(" "));
		return id;
	}

}
